iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 9
0

Get Request by POJO

範例:使用處理常式輸入/輸出的 POJO (Java)

照著AWS文件做就結束了
才不是,如果看完文件,大概很開心的就開始寫自己API想要的POJO,但是別忘了,我們現在的來源是API Gateway的Lambda Proxy integration處理過的內容,他將我們想要的body外包了一層http payload

首先我們來看一下之前log記錄到的reuqest完整內容

{ 
   httpMethod=POST,
   body=   { 
      "key1":"value1"
   },
   resource=/hello,
   requestContext=   { 
      resourceId=123456,
      apiId=1234567890,
      resourcePath=/hello,
      httpMethod=POST,
      requestId=c6af9ac6-7b61-11e6-9a41-93e8deadbeef,
      accountId=123456789012,
      stage=Prod,
      identity=      { 
         apiKey=null,
         userArn=null,
         cognitoAuthenticationType=null,
         caller=null,
         userAgent=Custom User Agent String,
         user=null,
         cognitoIdentityPoolId=null,
         cognitoAuthenticationProvider=null,
         sourceIp=127.0.0.1,
         accountId=null
      },
      extendedRequestId=null,
      path=/hello
   },
   queryStringParameters=null,
   multiValueQueryStringParameters=null,
   headers=   { 
      Cache-Control=no-cache,
      Postman-Token=4d185495-a813-47de-8631-c2bcfd27d30c,
      Content-Type=text/plain,
      User-Agent=PostmanRuntime/7.6.0,
      Accept=*/*,
      Host=localhost:3000,
      Accept-Encoding=gzip,
      deflate,
      Content-Length=17,
      Connection=keep-alive,
      X-Forwarded-Proto=http,
      X-Forwarded-Port=3000
   },
   multiValueHeaders=   { 
      Cache-Control=      [ 
         no-cache
      ],
      Postman-Token=      [ 
         4         d185495-a813-47de-8631-c2bcfd27d30c
      ],
      Content-Type=      [ 
         text/plain
      ],
      User-Agent=      [ 
         PostmanRuntime/7.6.0
      ],
      Accept=      [ 
         */*
      ],
      Host=      [ 
         localhost:3000
      ],
      Accept-Encoding=      [ 
         gzip,
         deflate
      ],
      Content-Length=      [ 
         17
      ],
      Connection=      [ 
         keep-alive
      ],
      X-Forwarded-Proto=      [ 
         http
      ],
      X-Forwarded-Port=      [ 
         3000
      ]
   },
   pathParameters=null,
   stageVariables=null,
   path=/hello,
   isBase64Encoded=false
}

我們先試著建立一個RequestBean如下

public class RequestBean {
    private String httpMethod;
    private Object body;
    private Map<String, String> queryStringParameters;
    private Map<String, String> headers;
    private Map<String, String> pathParameters;
    private boolean isBase64Encoded;
    
    public RequestBean() {}
}

你會注意到幾點

  1. 我沒有全部資料都打算承接,比如說headers與multiValueHeaders很明顯的就是相同的東西不同的deserialize方式,或是其他目前還沒打算用的部分,可以先省略掉。
  2. body型態是Object而非String,這是方便之後的處理方式,但使用String然後再自己deserialize也是可以。
  3. 所有變數都是private,這是因為接下來較建立get/set,當然全部改為public就可以直接拿來用了,不過有些問題還是建議要處理

然後,按下shift+alt+S -> R ->全選->產生
全部get/set都有了

headers name

這時候我們需要一些加工,首先,是headers
headers有甚麼問題了,http的header name定義其實是case-insensitive,而Java的Map預設是case-sensitive。你或許會想,http已經有訂標準的header name不是都是首字母大寫,或反正自訂header name只要明訂哪個字母要大寫,我知道該怎麼大小寫取的到不就好了嗎?

很不幸的,實作上會遇到以下幾個狀況(至少2019九月目前為止的版本)

  • AWS雲端的Lambda Proxy intergration不會特別轉換自訂header name,而自動產生的標準header name則是首字大寫。
  • SAM Local模擬Lambda Proxy intergration會自動將header name的首字母都轉為大寫,沒錯...你自訂的全小寫字母首字會被轉為大寫
  • shift language for ios,手機發送的的request header name全部會被轉為全小寫(這是一個被大家認為是設計bug好一段時間的東西,但是官方似乎沒打算修正)

所以,還是調整一下吧,我個人的方法是全轉小寫再來取值,然後另外新增一個getHeader的方法

public void setHeaders(Map<String, String> headers) {
    this.headers = new HashMap<String, String();
    headers.forEach((k, v) -> this.headers.put(k.toLowerCase(), v));
}

public String getHeader(String name) {
    name = name.toLowerCase();
    if (headers.containtsKey(name))
        return headers.get(name);
    return "";
}

或者你也可以用new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)代替?

body

重頭戲來啦我的身體呢?
先以簡單的內容來承接測試,並依此撰寫一個Person好了

public class Person {
    private String firstName;
    private String lastName;
    
    public Person() {}
}

並且一樣記得補上get/set

然後,把RequestBean.body改成Person type就好了嗎?
不行,我們會得到JsonMappingException

Can not instantiate value of type [simple type, class com.amazonaws.lambda.Beans.Person] from String value ('{"firstName": "Spencer", "lastName": "Liu"}'); no single-String constructor/factory method

這樣還需要擁有一個String為參數的建構子,而且要實作deserialize json,而且為了避免每種payload request就要對應一個RequestBean,或許也可以撰寫一個interface或super class等等,可是我覺得這樣還是太麻煩了!

所以撰寫一個getBody的多載,並且用Generic Types來處理如何?

public <T> T getBody(Class<T> bean) {
    return this.getBody(bean, null);
}

public <T> T getBody(Class<T> bean, Class<?> view) {
    if (body == null) return null;
    ObjectMapper mapper = new ObjectMapper();
    if (view != null)
        mapper.setConfig(mapper.getSerializationConfig().withView(view));
    try {
        return mapper.readValue(body.toString(), bean);
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        return null;
    }
}

於是乎,我們在handler當中想要取得body的內容,只要一行就可以了

Person person = request.getBody(Person.class);

這樣的多載改用了ObjectMapper.readValue,相較於原本的內容,我們可以用完整的Jackson功能,當然也包含annotation註釋的寫法。除此之外也預先準備了view的多載,在之後的文章將會進行應用的示範喔。


上一篇
Day08-概觀(四)AWS Lambda Function Handler in Java
下一篇
Day10-實作(二)Response
系列文
從Java進入AWS部署RESTful API的心路歷程30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言